﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.IO;
using System.Threading;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using SocialLearning.CoreInterfaces;
using SocialLearning;

namespace ArchitectureV2
{
    public class SocialLearningService : ISocialLearningService, ISocialLearningRestService
    {
        #region Fields

        private static ModuleLoader modules;
        private static Dictionary<SessionID, UserSession> users;
        private static Thread sessionCheck;

        #endregion

        #region Constructor

        static SocialLearningService()
        {
            modules = KnownTypes.Modules;
            users = new Dictionary<SessionID, UserSession>();
            sessionCheck = new Thread(new ThreadStart(CheckSessionExpiry));
            sessionCheck.Start();
        }

        #endregion

        #region Session Check Thread

        private static void CheckSessionExpiry()
        {
            List<SessionID> expired = new List<SessionID>();
            do
            {
                Thread.Sleep(30000);
                lock (users)
                {
                    foreach (KeyValuePair<SessionID, UserSession> pair in users)
                    {
                        if (pair.Value.IsExpired) { expired.Add(pair.Key); }
                    }
                    foreach (SessionID id in expired) { ExpireSession(id); }
                    expired.Clear();
                }
            }
            while (true);
        }

        #endregion

        #region Private Methods

        private static SessionID GenerateID(UserSession user)
        {
            lock (users)
            {
                SessionID sessionId = new SessionID(32);
                Random random = new Random();
                do { random.NextBytes(sessionId.ID); } while (users.ContainsKey(sessionId));
                users.Add(sessionId, user);
                return (sessionId);
            }
        }

        private static UserSession TryGetSession(byte[] id)
        {
            lock (users)
            {
                UserSession user;
                SessionID sessionId = new SessionID(id);
                if (users.TryGetValue(sessionId, out user) == false) { return (null); }
                return (user);
            }
        }

        private static UserSession GetSession(byte[] sessionId)
        {
            UserSession user = TryGetSession(sessionId);
            if (user == null) { throw new Exception("Session Not Found."); }
            return (user);
        }

        private static void ExpireSession(byte[] id) { ExpireSession(new SessionID(id)); }

        private static void ExpireSession(SessionID sessionId)
        {
            lock (users)
            {
                if (users.ContainsKey(sessionId))
                {
                    UserSession user = users[sessionId];
                    user.Logout();
                    users.Remove(sessionId);
                }
            }
        }

        #endregion

        #region Public Methods

        public byte[] Login(string username, string password)
        {
            UAC_Account user = null; // UAC_Engine.Login(username, password);
            if (username == "guest") { user = new UAC_Account(); } // Backdoor for debug.
            if (user == null) { return (null); }
            return (GenerateID(new UserSession(user, modules, username, password)).ID);
        }

        public void Logout(byte[] sessionId)
        {
            ExpireSession(sessionId);
        }

        public bool IsLoggedIn(byte[] sessionId)
        {
            return (TryGetSession(sessionId) != null);
        }

        public string[] GetModuleList(byte[] sessionId)
        {
            return (GetSession(sessionId).GetModuleList());
        }

        public string[] GetMethodList(byte[] sessionId, string module)
        {
            return (GetSession(sessionId).GetMethodList(module));
        }

        public User GetUser(byte[] sessionId)
        {
            UserSession session = GetSession(sessionId);
            return (new User(session.User, session.GetUserID()));
        }

        public User GetSomeUser(byte[] sessionId, string username)
        {
            return (new User(GetSession(sessionId).GetSomeUser(username), -1));
        }

        public void SetClientPingEndpoint(byte[] sessionId, string ip, int port)
        {
            GetSession(sessionId).SetClientPingEndpoint(ip, port);
        }

        public Result InvokeMethod(byte[] sessionId, string module, string method, object[] parameters)
        {
            Result result = new Result();
            try
            {
                // Invoke the module using reflection:
                result.ReturnValue = GetSession(sessionId).InvokeMethod(module, method, parameters);
            }
            catch (Exception x) { result.Error = x.Message; }
            return (result);
        }

        public AwesomeResult InvokeMethodAwesome(byte[] sessionId, string module, string method, string parameters)
        {
            AwesomeResult result = new AwesomeResult();
            object[] realParameters = null;
            if (parameters != null)
            {
                // Deserialise all parameters from JSON format:
                DataContractJsonSerializer serial = new DataContractJsonSerializer(typeof(object[]), KnownTypes.GetTypes(null));
                byte[] buffer = new byte[sizeof(char) * parameters.Length];
                Buffer.BlockCopy(parameters.ToCharArray(), 0, buffer, 0, buffer.Length);
                try { realParameters = serial.ReadObject(new MemoryStream(buffer)) as object[]; }
                catch { }
            }
            if (realParameters == null) { realParameters = new object[0]; }
            try
            {
                // Invoke the module using reflection:
                object x = GetSession(sessionId).InvokeMethod(module, method, realParameters);
                using (MemoryStream stream = new MemoryStream())
                {
                    // Serialise the return value back as a JSON string:
                    (new DataContractJsonSerializer(x.GetType(), KnownTypes.GetTypes(null))).WriteObject(stream, x);
                    stream.Position = 0;
                    result.ReturnValue = (new StreamReader(stream)).ReadToEnd();
                    stream.Close();
                }
            }
            catch (Exception x) { result.Error = x.Message; }
            return (result);
        }

        #endregion

        #region Debug Methods

        public int GetActivePingedUsers(byte[] sessionId)
        {
            return (GetSession(sessionId).GetActivePingedUsers());
        }

        #endregion
    }
}
